package com.kd.utils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * **************************************** From SpringFramework.
 *
 * @author Qiang.Liu
 * @create on 2008-11-24 下午04:25:26 ****************************************
 */
@SuppressWarnings("unchecked")
public class NumberUtils {

	/**
	 * Convert number to target class.
	 *
	 * @param number
	 *            the number
	 * @param targetClass
	 *            the target class
	 *
	 * @return the number
	 *
	 * @throws IllegalArgumentException
	 *             the illegal argument exception
	 */
	public static <T> T convertNumberToTargetClass(Number number,
			Class<T> targetClass) throws IllegalArgumentException {

//		Assert.notNull(number, "Number must not be null");
//		Assert.notNull(targetClass, "Target class must not be null");

		if (targetClass.isInstance(number)) {
			return (T) number;
		} else if (targetClass.equals(Byte.class)) {
			long value = number.longValue();
			if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
				raiseOverflowException(number, targetClass);
			}
			return (T) new Byte(number.byteValue());
		} else if (targetClass.equals(Short.class)) {
			long value = number.longValue();
			if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
				raiseOverflowException(number, targetClass);
			}
			return (T) new Short(number.shortValue());
		} else if (targetClass.equals(Integer.class)) {
			long value = number.longValue();
			if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
				raiseOverflowException(number, targetClass);
			}
			return (T) new Integer(number.intValue());
		} else if (targetClass.equals(Long.class)) {
			return (T) new Long(number.longValue());
		} else if (targetClass.equals(BigInteger.class)) {
			if (number instanceof BigDecimal) {
				return (T) ((BigDecimal) number).toBigInteger();
			} else {
				return (T) BigInteger.valueOf(number.longValue());
			}
		} else if (targetClass.equals(Float.class)) {
			return (T) new Float(number.floatValue());
		} else if (targetClass.equals(Double.class)) {
			return (T) new Double(number.doubleValue());
		} else if (targetClass.equals(BigDecimal.class)) {
			return (T) new BigDecimal(number.toString());
		} else {
			throw new IllegalArgumentException("Could not convert number ["
					+ number + "] of type [" + number.getClass().getName()
					+ "] to unknown target class [" + targetClass.getName()
					+ "]");
		}
	}

	/**
	 * Raise overflow exception.
	 *
	 * @param number
	 *            the number
	 * @param targetClass
	 *            the target class
	 */
	private static void raiseOverflowException(Number number, Class targetClass) {
		throw new IllegalArgumentException("Could not convert number ["
				+ number + "] of type [" + number.getClass().getName()
				+ "] to target class [" + targetClass.getName() + "]: overflow");
	}

	/**
	 * Parses the number.
	 *
	 * @param text
	 *            the text
	 * @param targetClass
	 *            the target class
	 *
	 * @return the number
	 */
	public static <T> T parseNumber(String text, Class<T> targetClass) {
//		Assert.notNull(text, "Text must not be null");
//		Assert.notNull(targetClass, "Target class must not be null");
		String trimmed = StringUtils.trimAllWhitespace(text);

		if (targetClass.equals(Byte.class)) {
			return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte
					.valueOf(trimmed));
		} else if (targetClass.equals(Short.class)) {
			return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short
					.valueOf(trimmed));
		} else if (targetClass.equals(Integer.class)) {
			return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer
					.valueOf(trimmed));
		} else if (targetClass.equals(Long.class)) {
			return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long
					.valueOf(trimmed));
		} else if (targetClass.equals(BigInteger.class)) {
			return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed)
					: new BigInteger(trimmed));
		} else if (targetClass.equals(Float.class)) {
			return (T) Float.valueOf(trimmed);
		} else if (targetClass.equals(Double.class)) {
			return (T) Double.valueOf(trimmed);
		} else if (targetClass.equals(BigDecimal.class)
				|| targetClass.equals(Number.class)) {
			return (T) new BigDecimal(trimmed);
		} else {
			throw new IllegalArgumentException("Cannot convert String [" + text
					+ "] to target class [" + targetClass.getName() + "]");
		}
	}

	public static <T> T safeParseNumber(String text, Class<T> targetClass) {
		return parseNumber(StringUtils.defaultString(text, "0"),targetClass);
	}

	/**
	 * Parses the number.
	 *
	 * @param text
	 *            the text
	 * @param targetClass
	 *            the target class
	 * @param numberFormat
	 *            the number format
	 *
	 * @return the number
	 */
	public static <T> T parseNumber(String text, Class<T> targetClass,
			NumberFormat numberFormat) {
		if (numberFormat != null) {
//			Assert.notNull(text, "Text must not be null");
//			Assert.notNull(targetClass, "Target class must not be null");
			DecimalFormat decimalFormat = null;
			boolean resetBigDecimal = false;
			if (numberFormat instanceof DecimalFormat) {
				decimalFormat = (DecimalFormat) numberFormat;
				if (BigDecimal.class.equals(targetClass)
						&& !decimalFormat.isParseBigDecimal()) {
					decimalFormat.setParseBigDecimal(true);
					resetBigDecimal = true;
				}
			}
			try {
				Number number = numberFormat.parse(StringUtils
						.trimAllWhitespace(text));
				return convertNumberToTargetClass(number, targetClass);
			} catch (ParseException ex) {
				IllegalArgumentException iae = new IllegalArgumentException(
						"Could not parse number: " + ex.getMessage());
				iae.initCause(ex);
				throw iae;
			} finally {
				if (resetBigDecimal) {
					decimalFormat.setParseBigDecimal(false);
				}
			}
		} else {
			return parseNumber(text, targetClass);
		}
	}

	/**
	 * Determine whether the given value String indicates a hex number, i.e.
	 * needs to be passed into <code>Integer.decode</code> instead of
	 * <code>Integer.valueOf</code> (etc).
	 *
	 * @param value
	 *            the value
	 *
	 * @return true, if checks if is hex number
	 */
	private static boolean isHexNumber(String value) {
		int index = (value.startsWith("-") ? 1 : 0);
		return (value.startsWith("0x", index) || value.startsWith("0X", index) || value
				.startsWith("#", index));
	}

	/**
	 * Decode a {@link java.math.BigInteger} from a {@link String} value.
	 * Supports decimal, hex and octal notation.
	 *
	 * @param value
	 *            the value
	 *
	 * @return the big integer
	 *
	 * @see BigInteger#BigInteger(String, int)
	 */
	private static BigInteger decodeBigInteger(String value) {
		int radix = 10;
		int index = 0;
		boolean negative = false;

		// Handle minus sign, if present.
		if (value.startsWith("-")) {
			negative = true;
			index++;
		}

		// Handle radix specifier, if present.
		if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
			index += 2;
			radix = 16;
		} else if (value.startsWith("#", index)) {
			index++;
			radix = 16;
		} else if (value.startsWith("0", index) && value.length() > 1 + index) {
			index++;
			radix = 8;
		}

		BigInteger result = new BigInteger(value.substring(index), radix);
		return (negative ? result.negate() : result);
	}

	public static BigDecimal add(Number source, Number target) {
		return run(source,'+',target);
	}

	public static BigDecimal reduce(Number source, Number target) {
		return run(source,'-',target);
	}
	public static BigDecimal multiply(Number source, Number target) {
		return run(source,'*',target);
	}
	public static BigDecimal divide(Number source, Number target) {
		return run(source,'/',target);
	}

	/**
	 * Parses the long.
	 *
	 * @param source
	 *            the source
	 *
	 * @return the long
	 */
	private static long parseLong(Long source) {
		return source == null ? 0 : source;
	}



	public static Integer getInteger(String number) {
		if (StringUtils.isNotBlank(number)){
			return (Integer) parseNumber(number, Integer.class);
		}
		return 0;
	}

	public static Short getShort(String number) {
		if (StringUtils.isNotBlank(number)){
			return (Short) parseNumber(number, Short.class);
		}
		return 0;
	}

	//大于等于
	public static boolean greatEquals(Number source,
			Number target) {
		return run(source,'-',target).doubleValue()>=0;
	}
	//大于
	public static boolean greatThan(Number source,
			Number target) {
		return run(source,'-',target).doubleValue()>0;
	}
	//小于
	public static boolean lessThan(Number source,
			Number target) {
		return run(source,'-',target).doubleValue()<0;
	}
	//小于等于
	public static boolean lessEquals(Number source,
			Number target) {
		return run(source,'-',target).doubleValue()<=0;
	}

	private static BigDecimal parseNotNullBigDecimal(Number source) {
		if (source==null){
			return new BigDecimal(0);
		}
		if (source.getClass()==BigDecimal.class){
			return (BigDecimal)source;
		}
		return (BigDecimal) convertNumberToTargetClass(source, BigDecimal.class);
	}

	public static double parseDouble(String number) {
		if (StringUtils.isNotBlank(number)){
			return parseNumber(number, Double.class).doubleValue();
		}
		return 0;
	}

	//取小數後n位
	public static double truncDouble(double value, int n) {
		double mi =  Math.pow(10, n);
		int tmp = (int)(value * mi);
		return tmp /mi;
	}

	public static String format(Number number,String pattern) {
		if (number==null)return null;
		return new DecimalFormat(pattern).format(number);
	}

	//取小數後n位
	public static String format(Number billRemAmt,int n) {
		StringBuffer sb = new StringBuffer("####");
		if (n>0){
			sb.append(".");
			for (int i = 0; i < n; i++) {
				sb.append('#');
			}
		}
		return format(billRemAmt,sb.toString());
	}
	


	public static BigDecimal run(Number source, char type, Number target) {

		if ('-'==type){
			return parseNotNullBigDecimal(source).subtract(parseNotNullBigDecimal(target));
		}
		if ('+'==type){
			return parseNotNullBigDecimal(source).add(parseNotNullBigDecimal(target));
		}
		if ('*'==type){
			return parseNotNullBigDecimal(source).multiply(parseNotNullBigDecimal(target));
		}
		if ('/'==type){
			return parseNotNullBigDecimal(source).divide(parseNotNullBigDecimal(target));
		}
		if ('^'==type){
			return parseNotNullBigDecimal(source).pow(parseNotNullBigDecimal(target).intValue());
		}
		if ('%'==type){
			return parseNotNullBigDecimal(source).remainder(parseNotNullBigDecimal(target));
		}
		return null;
	}

	public static BigDecimal parseNumber(String numberString) {
		numberString = StringUtils.trim(numberString);
		if (StringUtils.isBlank(numberString)){
			return null;
		}
		return safeParseNumber(numberString, BigDecimal.class);
	}

	public static int parseNumber(char ch) {
		switch (ch) {
		case '0':
			return 0x00;
		case '1':
			return 0x01;
		case '2':
			return 0x02;
		case '3':
			return 0x03;
		case '4':
			return 0x04;
		case '5':
			return 0x05;
		case '6':
			return 0x06;
		case '7':
			return 0x07;
		case '8':
			return 0x08;
		case '9':
			return 0x09;
		}
		return 0;
	}

	public static boolean isZero(BigDecimal billAmt) {
		return billAmt==null||billAmt.longValue()==0;
	}

	public static boolean greatThanZero(BigDecimal source) {
		return greatThan(source, 0);
	}

	public static long parseLong(String value) {
		return parseNumber(value).longValue();
	}

	public static Integer parseInt(String value) {
		try {
		if (StringUtils.isNotBlank(value)){
			return parseNumber(value).intValue();
		}
		} catch (Exception e) {
			// TODO: handle exception
		}
		return 0;
	}

	static Pattern NUMBER = Pattern.compile("([0-9]{1,})");
	public static Integer matchNumber(String str){
		if (str!=null){
			Matcher matcher = NUMBER.matcher(str);
			if (matcher.find()){
				Integer num = NumberUtils.parseInt(matcher.group(1));
				return num;
			}
		}
		return null;
	}

	public static boolean equals(Integer v1, Integer v2) {
		if (v1==null || v2 == null){
			return v1 == v2;
		}
		return v1.intValue() == v2.intValue();
	}


}
